using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;

namespace MKDS_Course_Editor.GCN.BCK
{
    class BCK
    {
        public class Ank1Header
        {
            public char[] tag; //'ANK1'
            public UInt32 sizeOfSection;

            /*
              0 - play once
              2 - loop
            */
            public byte loopFlags;

            public byte angleMultiplier; //all angles have to multiplied by pow(2, angleMultiplyer)

            public UInt16 animationLength; //in time units

            public UInt16 numJoints; //that many animated joints at offsetToJoints
            public UInt16 scaleCount; //that many floats at offsetToScales
            public UInt16 rotCount;   //that many s16s at offsetToRots
            public UInt16 transCount; //that many floats at offsetToTrans

            public UInt32 offsetToJoints;
            public UInt32 offsetToScales;
            public UInt32 offsetToRots;
            public UInt32 offsetToTrans;
        }


        //TODO: the following two structs have really silly names, rename them
        public class AnimIndex
        {
            public UInt16 count;
            public UInt16 index;
            public UInt16 zero; //always 0?? -> no (biawatermill01.bck) TODO: find out what it means
        }

        public class AnimComponent
        {
            public AnimIndex s = new AnimIndex(); //scale
            public AnimIndex r = new AnimIndex(); //rotation
            public AnimIndex t = new AnimIndex(); //translation
        }

        public class AnimatedJoint
        {
            /*
            if count > 1, count*3 floats/shorts stored at index
            (time, value, unk [interpolation info, e.g. tangent??])?

            for shorts, time is a "real" short, no fixedpoint
            */
            public AnimComponent x = new AnimComponent();
            public AnimComponent y = new AnimComponent();
            public AnimComponent z = new AnimComponent();
        }

        private static void readAnk1Header(EndianBinaryReader er, Ank1Header h)
        {
            h.tag = er.ReadChars(Encoding.ASCII, 4);
            h.sizeOfSection = er.ReadUInt32();
            h.loopFlags = er.ReadByte();
            h.angleMultiplier = er.ReadByte();
            h.animationLength = er.ReadUInt16();
            h.numJoints = er.ReadUInt16();
            h.scaleCount = er.ReadUInt16();
            h.rotCount = er.ReadUInt16();
            h.transCount = er.ReadUInt16();
            h.offsetToJoints = er.ReadUInt32();
            h.offsetToScales = er.ReadUInt32();
            h.offsetToRots = er.ReadUInt32();
            h.offsetToTrans = er.ReadUInt32();
        }

        private static void readAnimIndex(EndianBinaryReader er, AnimIndex h)
        {
            h.count = er.ReadUInt16();
            h.index = er.ReadUInt16();
            h.zero = er.ReadUInt16();
        }

        private static void readAnimComponent(EndianBinaryReader er, AnimComponent h)
        {
            readAnimIndex(er, h.s);
            readAnimIndex(er, h.r);
            readAnimIndex(er, h.t);
        }

        private static void readAnimatedJoint(EndianBinaryReader er, AnimatedJoint h)
        {
            readAnimComponent(er, h.x);
            readAnimComponent(er, h.y);
            readAnimComponent(er, h.z);
        }
        public class Key
        {
            public float time;
            public float value;
            public float tangent; //??
        }

        public class JointAnim
        {
            public List<Key> scalesX = new List<Key>();
            public List<Key> scalesY = new List<Key>();
            public List<Key> scalesZ = new List<Key>();

            public List<Key> rotationsX = new List<Key>();
            public List<Key> rotationsY = new List<Key>();
            public List<Key> rotationsZ = new List<Key>();

            public List<Key> translationsX = new List<Key>();
            public List<Key> translationsY = new List<Key>();
            public List<Key> translationsZ = new List<Key>();
        }

        public class Bck
        {
            public List<JointAnim> anims = new List<JointAnim>();
            public int animationLength;
            public string Name = "";
            public Ank1Header header;
            public void ToNSBCA(string f)
            {
                XmlTextWriter x =new XmlTextWriter(f, Encoding.GetEncoding("Shift_JIS"));
                x.Formatting = Formatting.Indented;
                x.Namespaces = false;
                x.WriteStartDocument();
                x.WriteStartElement("ica");
                x.WriteAttributeString("version", "1.6.0");
                {
                    x.WriteStartElement("head");
                    {
                        x.WriteStartElement("create");
                        x.WriteAttributeString("user", System.Windows.Forms.SystemInformation.UserName);
                        x.WriteAttributeString("host", System.Windows.Forms.SystemInformation.ComputerName);
                        x.WriteAttributeString("date", DateTime.Now.Year.ToString("0000") + "-" + DateTime.Now.Month.ToString("00") + "-" + DateTime.Now.Day.ToString("00") + "T" + DateTime.Now.Hour.ToString("00") + ":" + DateTime.Now.Minute.ToString("00") + ":" + DateTime.Now.Second.ToString("00"));
                        x.WriteAttributeString("source", Name);
                        x.WriteEndElement();
                        x.WriteStartElement("title");
                        {
                            x.WriteString("Character Animation Data for NINTENDO NITRO-System");
                        }
                        x.WriteEndElement();
                        x.WriteStartElement("generator");
                        x.WriteAttributeString("name", "MKDS Course Modifier");
                        x.WriteAttributeString("version", System.Windows.Forms.Application.ProductVersion);
                        x.WriteEndElement();
                    }
                    x.WriteEndElement();
                    x.WriteStartElement("body");
                    {
                        x.WriteStartElement("node_anm_info");
                        x.WriteAttributeString("frame_size", animationLength.ToString());
                        x.WriteAttributeString("scaling_rule", "maya");
                        x.WriteAttributeString("magnify", "1.000000");
                        x.WriteAttributeString("tool_start_frame", "1");
                        x.WriteAttributeString("tool_end_frame", animationLength.ToString());
                        x.WriteAttributeString("interpolation", "frame");
                        x.WriteAttributeString("compress_node", "none");
                        x.WriteAttributeString("node_size", (anims.Count + 2).ToString() + " " + (anims.Count + 2).ToString());
                        x.WriteAttributeString("frame_step_mode", "1");
                        x.WriteAttributeString("tolerance_scale", "0.100000");
                        x.WriteAttributeString("tolerance_rotate", "0.100000");
                        x.WriteAttributeString("tolerance_translate", "0.100000");
                        x.WriteEndElement();

                        x.WriteStartElement("node_scale_data");
                        //float rotScale = (float)Math.Pow(2.0f, header.angleMultiplier) / 32768.0f;
                        BMD.JNT1.Jnt1 jnt = new BMD.JNT1.Jnt1();
                        for (int j = 0; j < anims.Count; j++)
                        {
                            jnt.frames.Add(new BMD.JNT1.Frame());
                        }
                        string data = "1.000000";
                        int num = 1;
                        for (int k = 0; k < anims.Count; k++)
                        {
                            for (int i = 0; i < animationLength; i++)
                            {
                                BCK.animate(this, jnt, i);
                                num++;
                                data += " " + (jnt.frames[k].sx).ToString("0.000000").Replace(",", ".");
                            }
                            for (int i = 0; i < animationLength; i++)
                            {
                                BCK.animate(this, jnt, i);
                                num++;
                                data += " " + (jnt.frames[k].sy).ToString("0.000000").Replace(",", ".");
                            }
                            for (int i = 0; i < animationLength; i++)
                            {
                                BCK.animate(this, jnt, i);
                                num++;
                                data += " " + (jnt.frames[k].sz).ToString("0.000000").Replace(",", ".");
                            }
                        }
                        x.WriteAttributeString("size", num.ToString());
                        {
                            x.WriteString(data);
                        }
                        x.WriteEndElement();

                        x.WriteStartElement("node_rotate_data");
                        data = "0.000000";
                        num = 1;
                        for (int k = 0; k < anims.Count; k++)
                        {
                            for (int i = 0; i < animationLength; i++)
                            {
                                BCK.animate(this, jnt, i);
                                num++;
                                data += " " + jnt.frames[k].rx.ToString("0.000000").Replace(",", ".");
                            }
                            for (int i = 0; i < animationLength; i++)
                            {
                                BCK.animate(this, jnt, i);
                                num++;
                                data += " " + jnt.frames[k].ry.ToString("0.000000").Replace(",", ".");
                            }
                            for (int i = 0; i < animationLength; i++)
                            {
                                BCK.animate(this, jnt, i);
                                num++;
                                data += " " + jnt.frames[k].rz.ToString("0.000000").Replace(",", ".");
                            }
                        }
                        x.WriteAttributeString("size", num.ToString());
                        {
                            x.WriteString(data);
                        }
                        x.WriteEndElement();

                        x.WriteStartElement("node_translate_data");
                        data = "0.000000";
                        num = 1;
                        for (int k = 0; k < anims.Count; k++)
                        {
                            for (int i = 0; i < animationLength; i++)
                            {
                                BCK.animate(this, jnt, i);
                                num++;
                                data += " " + (jnt.frames[k].t.X/100).ToString("0.000000").Replace(",", ".");
                            }
                            for (int i = 0; i < animationLength; i++)
                            {
                                BCK.animate(this, jnt, i);
                                num++;
                                data += " " + (jnt.frames[k].t.Y/100).ToString("0.000000").Replace(",", ".");
                            }
                            for (int i = 0; i < animationLength; i++)
                            {
                                BCK.animate(this, jnt, i);
                                num++;
                                data += " " + (jnt.frames[k].t.Z/100).ToString("0.000000").Replace(",", ".");
                            }
                        }
                        x.WriteAttributeString("size", num.ToString());
                        {
                            x.WriteString(data);
                        }
                        x.WriteEndElement();

                        x.WriteStartElement("node_anm_array");
                        x.WriteAttributeString("size", (anims.Count + 2).ToString());
                        {
                            for (int i = 0; i < 2; i++)
                            {
                                x.WriteStartElement("node_anm");
                                x.WriteAttributeString("index", i.ToString());
                                {
                                    x.WriteStartElement("scale_x");
                                    x.WriteAttributeString("frame_step", "1");
                                    x.WriteAttributeString("data_size", "1");
                                    x.WriteAttributeString("data_head", "0");
                                    x.WriteEndElement();
                                    x.WriteStartElement("scale_y");
                                    x.WriteAttributeString("frame_step", "1");
                                    x.WriteAttributeString("data_size", "1");
                                    x.WriteAttributeString("data_head", "0");
                                    x.WriteEndElement();
                                    x.WriteStartElement("scale_z");
                                    x.WriteAttributeString("frame_step", "1");
                                    x.WriteAttributeString("data_size", "1");
                                    x.WriteAttributeString("data_head", "0");
                                    x.WriteEndElement();

                                    x.WriteStartElement("rotate_x");
                                    x.WriteAttributeString("frame_step", "1");
                                    x.WriteAttributeString("data_size", "1");
                                    x.WriteAttributeString("data_head", "0");
                                    x.WriteEndElement();
                                    x.WriteStartElement("rotate_y");
                                    x.WriteAttributeString("frame_step", "1");
                                    x.WriteAttributeString("data_size", "1");
                                    x.WriteAttributeString("data_head", "0");
                                    x.WriteEndElement();
                                    x.WriteStartElement("rotate_z");
                                    x.WriteAttributeString("frame_step", "1");
                                    x.WriteAttributeString("data_size", "1");
                                    x.WriteAttributeString("data_head", "0");
                                    x.WriteEndElement();

                                    x.WriteStartElement("translate_x");
                                    x.WriteAttributeString("frame_step", "1");
                                    x.WriteAttributeString("data_size", "1");
                                    x.WriteAttributeString("data_head", "0");
                                    x.WriteEndElement();
                                    x.WriteStartElement("translate_y");
                                    x.WriteAttributeString("frame_step", "1");
                                    x.WriteAttributeString("data_size", "1");
                                    x.WriteAttributeString("data_head", "0");
                                    x.WriteEndElement();
                                    x.WriteStartElement("translate_z");
                                    x.WriteAttributeString("frame_step", "1");
                                    x.WriteAttributeString("data_size", "1");
                                    x.WriteAttributeString("data_head", "0");
                                    x.WriteEndElement();
                                }
                                x.WriteEndElement();
                            }
                            int scale = 1;
                            int rot = 1;
                            int trans = 1;
                            for (int i = 0; i < anims.Count; i++)
                            {
                                x.WriteStartElement("node_anm");
                                x.WriteAttributeString("index", (i + 2).ToString());
                                {
                                    x.WriteStartElement("scale_x");
                                    x.WriteAttributeString("frame_step", "1");
                                    x.WriteAttributeString("data_size", animationLength.ToString());
                                    x.WriteAttributeString("data_head", scale.ToString());
                                    x.WriteEndElement();
                                    scale += animationLength;
                                    x.WriteStartElement("scale_y");
                                    x.WriteAttributeString("frame_step", "1");
                                    x.WriteAttributeString("data_size", animationLength.ToString());
                                    x.WriteAttributeString("data_head", scale.ToString());
                                    x.WriteEndElement();
                                    scale += animationLength;
                                    x.WriteStartElement("scale_z");
                                    x.WriteAttributeString("frame_step", "1");
                                    x.WriteAttributeString("data_size", animationLength.ToString());
                                    x.WriteAttributeString("data_head", scale.ToString());
                                    x.WriteEndElement();
                                    scale += animationLength;

                                    x.WriteStartElement("rotate_x");
                                    x.WriteAttributeString("frame_step", "1");
                                    x.WriteAttributeString("data_size", animationLength.ToString());
                                    x.WriteAttributeString("data_head", rot.ToString());
                                    x.WriteEndElement();
                                    rot += animationLength;
                                    x.WriteStartElement("rotate_y");
                                    x.WriteAttributeString("frame_step", "1");
                                    x.WriteAttributeString("data_size", animationLength.ToString());
                                    x.WriteAttributeString("data_head", rot.ToString());
                                    x.WriteEndElement();
                                    rot += animationLength;
                                    x.WriteStartElement("rotate_z");
                                    x.WriteAttributeString("frame_step", "1");
                                    x.WriteAttributeString("data_size", animationLength.ToString());
                                    x.WriteAttributeString("data_head", rot.ToString());
                                    x.WriteEndElement();
                                    rot += animationLength;

                                    x.WriteStartElement("translate_x");
                                    x.WriteAttributeString("frame_step", "1");
                                    x.WriteAttributeString("data_size", animationLength.ToString());
                                    x.WriteAttributeString("data_head", trans.ToString());
                                    x.WriteEndElement();
                                    trans += animationLength;
                                    x.WriteStartElement("translate_y");
                                    x.WriteAttributeString("frame_step", "1");
                                    x.WriteAttributeString("data_size", animationLength.ToString());
                                    x.WriteAttributeString("data_head", trans.ToString());
                                    x.WriteEndElement();
                                    trans += animationLength;
                                    x.WriteStartElement("translate_z");
                                    x.WriteAttributeString("frame_step", "1");
                                    x.WriteAttributeString("data_size", animationLength.ToString());
                                    x.WriteAttributeString("data_head", trans.ToString());
                                    x.WriteEndElement();
                                    trans += animationLength;
                                }
                                x.WriteEndElement();
                            }
                        }
                        x.WriteEndElement();
                    }
                    x.WriteEndElement();
                }
                x.WriteEndElement();
                x.Close();
            }
        }

        private static void readComp(List<Key> dst, List<float> src, AnimIndex index)
        {
            //dst.resize(index.count);
            dst.AddRange(new Key[index.count]);
            //violated by biawatermill01.bck
            if (index.zero != 0)
            {
                //warn("bck: zero field %d instead of zero", index.zero);
            }
            //TODO: biawatermill01.bck doesn't work, so the "zero"
            //value is obviously something important

            if (index.count <= 0)
            {
                //warn("bck1: readComp(): count is <= 0");
                return;
            }
            else if (index.count == 1)
            {
                dst[0] = new Key();
                dst[0].time = 0;
                dst[0].value = src[index.index];
                dst[0].tangent = 0;
            }
            else
            {
                for (int j = 0; j < index.count; ++j)
                {
                    dst[j] = new Key();
                    dst[j].time = src[index.index + 3 * j];
                    dst[j].value = src[index.index + 3 * j + 1];
                    dst[j].tangent = src[index.index + 3 * j + 2];
                }
            }
        }
        private static void readComp(List<Key> dst, List<Int16> src, AnimIndex index)
        {
            //dst.resize(index.count);
            dst.AddRange(new Key[index.count]);
            //violated by biawatermill01.bck
            if (index.zero != 0)
            {
                //warn("bck: zero field %d instead of zero", index.zero);
            }
            //TODO: biawatermill01.bck doesn't work, so the "zero"
            //value is obviously something important

            if (index.count <= 0)
            {
                //warn("bck1: readComp(): count is <= 0");
                return;
            }
            else if (index.count == 1)
            {
                dst[0] = new Key();
                dst[0].time = 0;
                dst[0].value = src[index.index];
                dst[0].tangent = 0;
            }
            else
            {
                for (int j = 0; j < index.count; ++j)
                {
                    dst[j] = new Key();
                    dst[j].time = src[index.index + 3 * j];
                    dst[j].value = src[index.index + 3 * j + 1];
                    dst[j].tangent = src[index.index + 3 * j + 2];
                }
            }
        }

        private static void convRotation(List<Key> rots, float scale)
        {
            for (int j = 0; j < rots.Count; ++j)
            {
                rots[j].value *= scale;
                rots[j].tangent *= scale;
            }
        }

        private static void dumpAnk1(EndianBinaryReader er, Bck bck)
        {
            int i;
            long ank1Offset = er.BaseStream.Position;//ftell(f);

            //read header
            Ank1Header h = new Ank1Header();
            readAnk1Header(er, h);

            bck.animationLength = h.animationLength;

            //read scale floats:
            er.BaseStream.Seek(ank1Offset + h.offsetToScales, SeekOrigin.Begin);
            List<float> scales = new List<float>();
            for(i = 0; i < h.scaleCount; ++i)
            {
                scales.Add(er.ReadSingle());
                //Helper.toFLOAT(scales[i]);
            }

            //read rotation s16s:
            er.BaseStream.Seek(ank1Offset + h.offsetToRots, SeekOrigin.Begin);
            List<Int16> rotations = new List<short>();
            rotations.AddRange(er.ReadInt16s(h.rotCount));
            for (i = 0; i < h.rotCount; ++i)
            {
                Helper.toSHORT((ushort)rotations[i]);
            }

            //read translation floats:
            er.BaseStream.Seek(ank1Offset + h.offsetToTrans, SeekOrigin.Begin);
            List<float> translations = new List<float>();
            for (i = 0; i < h.transCount; ++i)
            {
                translations.Add(er.ReadSingle());//.ReadInt32());
                //Helper.toFLOAT(translations[i]);
            }

            //read joints
            float rotScale = (float)Math.Pow(2.0f, h.angleMultiplier) * 180 / 32768.0f;
            er.BaseStream.Seek(ank1Offset + h.offsetToJoints, SeekOrigin.Begin);
            bck.anims = new List<JointAnim>();
            bck.anims.AddRange(new JointAnim[h.numJoints]);
            for (i = 0; i < h.numJoints; ++i)
            {
                AnimatedJoint joint = new AnimatedJoint();
                readAnimatedJoint(er, joint);

                bck.anims[i] = new JointAnim();

                readComp(bck.anims[i].scalesX, scales, joint.x.s);
                readComp(bck.anims[i].scalesY, scales, joint.y.s);
                readComp(bck.anims[i].scalesZ, scales, joint.z.s);

                readComp(bck.anims[i].rotationsX, rotations, joint.x.r);
                convRotation(bck.anims[i].rotationsX, rotScale);
                readComp(bck.anims[i].rotationsY, rotations, joint.y.r);
                convRotation(bck.anims[i].rotationsY, rotScale);
                readComp(bck.anims[i].rotationsZ, rotations, joint.z.r);
                convRotation(bck.anims[i].rotationsZ, rotScale);

                readComp(bck.anims[i].translationsX, translations, joint.x.t);
                readComp(bck.anims[i].translationsY, translations, joint.y.t);
                readComp(bck.anims[i].translationsZ, translations, joint.z.t);
            }
            bck.header = h;
            //TODO: some zelda files store additional data after
            //the ANK1 block, for example 24_cl_cut07_dash_o.bck
        }

        public static Bck readBck(string f)
        {
            EndianBinaryReader er = new EndianBinaryReader(File.OpenRead(f), Endianness.BigEndian);
            Bck ret = new Bck();
            ret.Name = Path.GetFileNameWithoutExtension(f);
            //skip file header
            er.BaseStream.Position = 0x20;

            UInt32 size = 0;
            char[] tag = new char[4];
            int t;

            do
            {
                er.BaseStream.Seek(size, SeekOrigin.Current);
                t = (int)er.BaseStream.Position;

                tag = er.ReadChars(Encoding.ASCII, 4);
                size = er.ReadUInt32();
                Helper.toDWORD(size);
                if (size < 8) size = 8; //prevent endless loop on corrupt data

                if (er.BaseStream.Position >= er.BaseStream.Length)
                {
                    break;
                }

                er.BaseStream.Position = t;

                if (tag[0] == 'A' &&  tag[1] == 'N' && tag[2] == 'K' && tag[3] == '1')
                    dumpAnk1(er, ret);
                else
                {
                    //warn("readBck(): Unsupported section \'%c%c%c%c\'",
                    //   tag[0], tag[1], tag[2], tag[3]);
                }

                //fseek(f, t, SEEK_SET);
                er.BaseStream.Position = t;
            } while (!(er.BaseStream.Position >= er.BaseStream.Length));
            er.Close();
            return ret;
        }

        public static float interpolate(float v1, float d1, float v2, float d2, float t) //t in [0, 1]
        {
            //linear interpolation
            //return v1 + t*(v2 - v1);

            //cubic interpolation
            float a = 2 * (v1 - v2) + d1 + d2;
            float b = -3 * v1 + 3 * v2 - 2 * d1 - d2;
            float c = d1;
            float d = v1;
            //TODO: yoshi_walk.bck has strange-looking legs...not sure if
            //the following line is to blame, though
            return ((a * t + b) * t + c) * t + d;
        }

        public static float getAnimValue(List<Key> keys, float t)
        {
            if (keys.Count == 0)
                return 0.0f;

            if (keys.Count == 1)
                return keys[0].value;

            int i = 1;
            while (keys[i].time < t)
                ++i;

            float time = (t - keys[i - 1].time) / (keys[i].time - keys[i - 1].time); //scale to [0, 1]
            return interpolate(keys[i - 1].value, keys[i - 1].tangent, keys[i].value, keys[i].tangent, time);
        }

        //the caller has to ensure that jnt1.frames and bck.anims contain
        //the same number of elements
        public static void animate(Bck bck, MKDS_Course_Editor.GCN.BMD.JNT1.Jnt1 jnt1, float ftime)
        {
            ftime = ftime % bck.animationLength;
            
            //update joints
            for (int i = 0; i < jnt1.frames.Count; ++i)
            {
                jnt1.frames[i].sx = getAnimValue(bck.anims[i].scalesX, ftime);
                jnt1.frames[i].sy = getAnimValue(bck.anims[i].scalesY, ftime);
                jnt1.frames[i].sz = getAnimValue(bck.anims[i].scalesZ, ftime);

                //TODO: use quaternion interpolation for rotations?
                jnt1.frames[i].rx = getAnimValue(bck.anims[i].rotationsX, ftime);
                jnt1.frames[i].ry = getAnimValue(bck.anims[i].rotationsY, ftime);
                jnt1.frames[i].rz = getAnimValue(bck.anims[i].rotationsZ, ftime);

                jnt1.frames[i].t.X = getAnimValue(bck.anims[i].translationsX, ftime);
                jnt1.frames[i].t.Y = getAnimValue(bck.anims[i].translationsY, ftime);
                jnt1.frames[i].t.Z = getAnimValue(bck.anims[i].translationsZ, ftime);
            }

        }
    }
}
